#pragma once
#ifndef __HWINVARIANT_H__
#define __HWINVARIANT_H__

#include "hwindef.h"
#include "hwincom.h"
#include "hwinexception.h"
#include "hwindatetime.h"


namespace harlinn
{
    namespace windows
    {
        // ----------------------------------------------------------------------
        // VariantType
        // ----------------------------------------------------------------------
        enum class VariantType : unsigned short
        {
            Empty	= 0,
            Null	= 1,
            Int2	= 2,
            Short	= 2,
            Int4	= 3,
            Long	= 3,
            Real4	= 4,
            Float	= 4,
            Real8	= 5,
            Double	= 5,
            Currency	= 6,
            DateTime = 7,
            BStr	= 8,
            Dispatch	= 9,
            Error	= 10,
            Bool	= 11,
            Variant	= 12,
            Unknown	= 13,
            Decimal	= 14,
            Int1	= 16,
            SByte	= 16,
            UInt1	= 17,
            Byte	= 17,
            UInt2	= 18,
            UShort	= 18,
            UInt4	= 19,
            ULong	= 19,
            Int8	= 20,
            Int64	= 20,
            LongLong	= 20,
            UInt8	= 21,
            UInt64	= 21,
            ULongLong	= 21,
            Int	= 22,
            UInt	= 23,
            Void	= 24,
            HResult	= 25,
            Pointer	= 26,
            SafeArray	= 27,
            CArray	= 28,
            UserDefined	= 29,
            LPStr	= 30,
            LPWStr	= 31,
            Record	= 36,
            IntPtr	= 37,
            UIntPtr	= 38,
            FileTime	= 64,
            Blob	= 65,
            Stream	= 66,
            Storage	= 67,
            StreamedObject	= 68,
            StoredObject	= 69,
            BlobObject	= 70,
            CF	= 71,
            CLSID	= 72,
            VersionedStream	= 73,
            BStrBlob	= 0xfff,
            Vector	= 0x1000,
            Array	= 0x2000,
            Byref	= 0x4000,
            Reserved	= 0x8000,
            Illegal	= 0xffff,
            IllegalMasked	= 0xfff,
            TypeMask	= 0xfff
        };
        DEFINE_ENUM_FLAG_OPERATORS(VariantType)

        // ----------------------------------------------------------------------
        // SysString
        // ----------------------------------------------------------------------
        class SysString
        {
            BSTR bstr;
        public:

            static BSTR Copy(const BSTR bstr)
            {
                HWIN_TRACE();
                if(bstr)
                {
                    UINT length = SysStringLen(bstr);
                    auto result = SysAllocStringLen(bstr,length);
                    if((!result) && length)
                    {
                        CheckHRESULT(E_OUTOFMEMORY);
                    }
                    return result;
                }
                return nullptr;
            }

            SysString()
                : bstr(0)
            {
                HWIN_TRACE();
            }

            SysString(const String& theString)
                : bstr(0)
            {
                HWIN_TRACE();
                bstr = SysAllocStringLen(theString.c_str(),UINT(theString.length()) );
                if((bstr == nullptr)&&(theString.length()))
                {
                    CheckHRESULT(E_OUTOFMEMORY);
                }
            }

            SysString(const wchar_t* theString, bool attach = false)
                : bstr(0)
            {
                HWIN_TRACE();
                if(!attach)
                {
                    bstr = SysAllocString(theString);
                    if((bstr == nullptr)&& theString && theString[0])
                    {
                        CheckHRESULT(E_OUTOFMEMORY);
                    }
                }
                else
                {
                    bstr = (BSTR)theString;
                }
            }

            SysString(const wchar_t* theString, size_t theLength)
                : bstr(0)
            {
                HWIN_TRACE();
                bstr = SysAllocStringLen(theString,UINT(theLength));
                if((bstr == nullptr)&& theString && theString[0])
                {
                    CheckHRESULT(E_OUTOFMEMORY);
                }
            }

            SysString( const SysString& other)
                : bstr(Copy(other.bstr))
            {
                HWIN_TRACE();
            }

            SysString( SysString&& other)
                : bstr(other.bstr)
            {
                HWIN_TRACE();
                other.bstr = nullptr;
            }


            ~SysString()
            {
                HWIN_TRACE();
                if(bstr)
                {
                    SysFreeString(bstr);
                }
            }


            SysString& operator = ( const SysString& other)
            {
                HWIN_TRACE();
                if(bstr != other.bstr)
                {
                    if(bstr)
                    {
                        SysFreeString(bstr);
                        bstr = nullptr;
                    }
                    bstr = Copy(other.bstr);
                }
                return *this;
            }

            SysString& operator = ( SysString&& other)
            {
                HWIN_TRACE();
                if(this != &other)
                {
                    if(bstr)
                    {
                        SysFreeString(bstr);
                        bstr = nullptr;
                    }
                    bstr = other.bstr;
                    other.bstr = nullptr;
                }
                return *this;
            }

            SysString& Attach(BSTR theString)
            {
                HWIN_TRACE();
                if(bstr != theString)
                {
                    if(bstr)
                    {
                        SysFreeString(bstr);
                        bstr = nullptr;
                    }
                    bstr = theString;
                }
                return *this;
            }

            BSTR Detach()
            {
                HWIN_TRACE();
                auto result = bstr;
                bstr = nullptr;
                return result;
            }

            

            BSTR Copy( ) const
            {
                HWIN_TRACE();
                return Copy(bstr);
            }


            const BSTR c_str() const
            {
                HWIN_TRACE();
                return bstr;
            }

            BSTR c_str()
            {
                HWIN_TRACE();
                return bstr;
            }

            size_t length() const
            {
                HWIN_TRACE();
                if(bstr)
                {
                    return size_t(SysStringLen(bstr));
                }
                return 0;
            }

            OLECHAR At(size_t index) const
            {
                HWIN_TRACE();
                return bstr[index];
            }

            OLECHAR& At(size_t index)
            {
                HWIN_TRACE();
                return bstr[index];
            }

            OLECHAR operator [](size_t index) const
            {
                HWIN_TRACE();
                return bstr[index];
            }

            OLECHAR& operator [](size_t index)
            {
                HWIN_TRACE();
                return bstr[index];
            }

        };


        // ----------------------------------------------------------------------
        // SafeArray
        // ----------------------------------------------------------------------
        class SafeArray
        {
            SAFEARRAY* ptr;
        public:
            static VariantType GetElementVariantType(SAFEARRAY* safeArray)
            {
                HWIN_TRACE();
                if(safeArray)
                {
                    if(safeArray->fFeatures & FADF_DISPATCH)
                    {
                        return VariantType::Dispatch;
                    }
                    if(safeArray->fFeatures & FADF_UNKNOWN)
                    {
                        return VariantType::Unknown;
                    }
                    if(safeArray->fFeatures & FADF_VARIANT)
                    {
                        return VariantType::Variant;
                    }
                    if(safeArray->fFeatures & FADF_BSTR)
                    {
                        return VariantType::BStr;
                    }
                    VARTYPE vt = 0;
                    auto hr = SafeArrayGetVartype(safeArray,&vt);
                    if(FAILED(hr))
                    {
                        CheckHRESULT(hr);
                    }
                }
                else
                {
                    return VariantType::Empty;
                }
            }

            // ----------------------------------------------------------------------
            // Lock
            // ----------------------------------------------------------------------
            friend class SafeArrayLock;
            class SafeArrayLock
            {
                SAFEARRAY* safeArray;
            public:
                SafeArrayLock(SafeArray& theSafeArray)
                {
                    HWIN_TRACE();
                    safeArray = theSafeArray.ptr;
                    auto hr = ::SafeArrayLock(safeArray);
                    if(FAILED(hr))
                    {
                        CheckHRESULT(hr);
                    }
                }

                ~SafeArrayLock()
                {
                    HWIN_TRACE();
                    if(safeArray)
                    {
                        SafeArrayUnlock(safeArray);
                    }
                }
            };
            typedef SafeArrayLock Lock;


            // ----------------------------------------------------------------------
            // DataLock
            // ----------------------------------------------------------------------
            class DataLock
            {
                SAFEARRAY* safeArray;
                void* data;
            public:
                DataLock(SafeArray& theSafeArray)
                {
                    HWIN_TRACE();
                    safeArray = theSafeArray.ptr;
                    auto hr = SafeArrayAccessData(safeArray,&data);
                    if(FAILED(hr))
                    {
                        CheckHRESULT(hr);
                    }
                }

                ~DataLock()
                {
                    HWIN_TRACE();
                    if(safeArray)
                    {
                        SafeArrayUnaccessData(safeArray);
                    }
                }

                template<typename T>
                T* Data()
                {
                    HWIN_TRACE();
                    return reinterpret_cast<T*>(data);
                }


            };



            SafeArray()
                : ptr(0)
            {
                HWIN_TRACE();
            }

            SafeArray(VariantType variantType, UINT numberOfElements)
                : ptr(0)
            {
                HWIN_TRACE();
                SAFEARRAYBOUND rgsabound = {numberOfElements,0};
                ptr = SafeArrayCreate(VARTYPE(variantType),1,&rgsabound);
                if(!ptr)
                {
                    CheckHRESULT(E_OUTOFMEMORY);
                }
            }

            SafeArray(const SafeArray& other)
                : ptr(0)
            {
                HWIN_TRACE();
                if(other.ptr)
                {
                    auto hr = SafeArrayCopy(other.ptr,&ptr);
                    if(FAILED(hr))
                    {
                        CheckHRESULT(hr);
                    }
                }
            }


            SafeArray(SafeArray&& other)
                : ptr(other.ptr)
            {
                HWIN_TRACE();
                other.ptr = nullptr;
            }


            ~SafeArray()
            {
                HWIN_TRACE();
                if(ptr)
                {
                    auto hr = SafeArrayDestroy(ptr);
                    CheckHRESULT(hr);
                }
            }



            SafeArray& operator = (const SafeArray& other)
            {
                HWIN_TRACE();
                if(other.ptr && (ptr != other.ptr))
                {
                    if(ptr)
                    {
                        auto hr = SafeArrayDestroy(ptr);
                        CheckHRESULT(hr);
                        ptr = nullptr;
                    }
                    auto hr = SafeArrayCopy(other.ptr,&ptr);
                    if(FAILED(hr))
                    {
                        CheckHRESULT(hr);
                    }
                }
                return *this;
            }

            SafeArray& operator = (SafeArray&& other)
            {
                HWIN_TRACE();
                if(this != &other)
                {
                    if(ptr)
                    {
                        auto hr = SafeArrayDestroy(ptr);
                        CheckHRESULT(hr);
                        ptr = nullptr;
                    }
                    ptr = other.ptr;
                    other.ptr = nullptr;
                }
                return *this;
            }


        };


        

        // ----------------------------------------------------------------------
        // Variant
        // ----------------------------------------------------------------------
        class Variant : public VARIANT
        {
            void SetVariantType(VariantType variantType)
            {
                HWIN_TRACE();
                vt = USHORT(variantType);
            }
        public:
            static int Compare(const VARIANT& first,const VARIANT& second)
            {
                HWIN_TRACE();
                if(first.vt == USHORT(VariantType::Empty))
                {
                    if(second.vt == USHORT(VariantType::Empty))
                    {
                        return 0;
                    }
                    return -1;
                }
                else if(second.vt == USHORT(VariantType::Empty))
                {
                    return 1;
                }
                else if(first.vt == USHORT(VariantType::Null))
                {
                    if(second.vt == USHORT(VariantType::Null))
                    {
                        return 0;
                    }
                    return -1;
                }
                else if(second.vt == USHORT(VariantType::Null))
                {
                    return 1;
                }

                auto lcid = GetUserDefaultLCID();
                auto result = VarCmp((VARIANT*)&first,(VARIANT*)&second,lcid);
                if(FAILED(result))
                {
                    CheckHRESULT(result);
                }
                return result - 1;
            }

            VariantType Type() const
            {
                HWIN_TRACE();
                return VariantType(vt);
            }

            VariantType ElementType() const
            {
                HWIN_TRACE();
                return VariantType(vt & VT_TYPEMASK);
            }

            bool IsEmpty() const
            {
                HWIN_TRACE();
                return Type() == VariantType::Empty;
            }

            bool IsNull() const
            {
                HWIN_TRACE();
                return Type() == VariantType::Null;
            }

            bool IsReference() const
            {
                HWIN_TRACE();
                return (Type() & VariantType::Byref) != VariantType::Empty;
            }

            bool IsArray() const
            {
                HWIN_TRACE();
                return (Type() & VariantType::Array) != VariantType::Empty;
            }



            Variant()
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Empty);
            }
            Variant(INT64 theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Int64);
                llVal = theValue;
            }
            Variant(UINT64 theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::UInt64);
                ullVal = theValue;
            }
            Variant(LONG theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Long);
                lVal = theValue;
            }

            Variant(LONG theValue, VariantType variantType )
            {
                HWIN_TRACE();
                if( variantType == VariantType::Long || variantType == VariantType::Error )
                {
                    SetVariantType(variantType);
                    lVal = theValue;
                }
                else
                {
                    SetVariantType(VariantType::Error);
                    scode = E_INVALIDARG;
                    CheckHRESULT(scode);
                }
            }

            Variant(BYTE theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Byte);
                bVal = theValue;
            }
            Variant(SHORT theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Short);
                iVal = theValue;
            }
            Variant(FLOAT theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Float);
                fltVal = theValue;
            }
            Variant(DOUBLE theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Double);
                dblVal = theValue;
            }

            Variant(DOUBLE theValue, VariantType variantType )
            {
                HWIN_TRACE();
                if( variantType == VariantType::Double || variantType == VariantType::DateTime )
                {
                    SetVariantType(variantType);
                    dblVal = theValue;
                }
                else
                {
                    SetVariantType(VariantType::Error);
                    scode = E_INVALIDARG;
                    CheckHRESULT(scode);
                }
            }

            Variant(bool theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Bool);
                boolVal = theValue?VARIANT_TRUE:VARIANT_FALSE;
            }

            Variant(const CY& theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Currency);
                cyVal = theValue;
            }
            Variant(const DateTime& theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::DateTime);
                date = theValue.ToOADate();
            }
            Variant(const TimeSpan& theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::DateTime);
                date = theValue.TotalDays();
            }


            Variant(const SysString& theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::BStr);
                bstrVal = theValue.Copy();
            }

            Variant(const String& theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::BStr);
                bstrVal = nullptr;
                if(theValue.length())
                {
                    bstrVal = SysAllocStringLen(theValue.c_str(),UINT(theValue.length()));
                    if(!bstrVal)
                    {
                        CheckHRESULT(E_OUTOFMEMORY);
                    }
                }
            }


            Variant(IUnknown* pInterface)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Unknown);
                punkVal = pInterface;
                if(punkVal)
                {
                    punkVal->AddRef();
                }
            }

            Variant(IDispatch* pInterface)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Dispatch);
                pdispVal = pInterface;
                if(pdispVal)
                {
                    pdispVal->AddRef();
                }
            }

            Variant(SAFEARRAY* safeArray)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Array);
                if(safeArray)
                {
                    auto vType = SafeArray::GetElementVariantType(safeArray);
                    SetVariantType(VariantType::Array | vType);
                    auto hr = SafeArrayCopy(safeArray,&parray);
                    CheckHRESULT(hr);
                }
                else
                {
                    parray = nullptr;
                }
            }

            Variant(CHAR theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::SByte);
                cVal = theValue;
            }

            Variant(USHORT theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::UShort);
                uiVal = theValue;
            }

            Variant(ULONG theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::ULong);
                ulVal = theValue;
            }




            Variant(const Variant& other)
            {
                HWIN_TRACE();
                VariantInit(this);
                auto hr = VariantCopy(this,&other);
                CheckHRESULT(hr);
            }

            Variant(Variant&& other)
            {
                HWIN_TRACE();
                memcpy(&vt,&other.vt,sizeof(VARIANT));
                memset(&other.vt,0,sizeof(VARIANT));
            }

            Variant& Clear()
            {
                HWIN_TRACE();
                switch(vt)
                {
                case VT_EMPTY:
                case VT_NULL:
                case VT_I1:
                case VT_I2:
                case VT_I4:
                case VT_I8:
                case VT_UI1:
                case VT_UI2:
                case VT_UI4:
                case VT_UI8:
                case VT_R4:
                case VT_R8:
                case VT_CY:
                case VT_DATE:
                    vt = VT_EMPTY;
                    break;
                default:
                    auto hr = VariantClear(this);
                    CheckHRESULT(hr);
                    break;

                }
                return *this;
            }

            ~Variant()
            {
                HWIN_TRACE();
                if(vt != VT_EMPTY)
                {
                    Clear();
                }
            }

            Variant& operator = (const Variant& other)
            {
                HWIN_TRACE();
                if(&vt != &other.vt)
                {
                    if(vt != VT_EMPTY)
                    {
                        auto hr = VariantClear(this);
                        CheckHRESULT(hr);
                    }
                    auto hr = VariantCopy(this,&other);
                    CheckHRESULT(hr);
                }
                return *this;
            }

            Variant& operator = (Variant&& other)
            {
                HWIN_TRACE();
                if(this != &other)
                {
                    if(vt != VT_EMPTY)
                    {
                        auto hr = VariantClear(this);
                        CheckHRESULT(hr);
                    }
                    memcpy(&vt,&other.vt,sizeof(VARIANT));
                    memset(&other.vt,0,sizeof(VARIANT));
                }
            }

            int CompareTo(const VARIANT& other) const
            {
                HWIN_TRACE();
                return Compare(*this,other);
            }

            int CompareTo(const Variant& other) const
            {
                HWIN_TRACE();
                return Compare(*this,other);
            }

            bool operator == (const VARIANT& other) const { HWIN_TRACE();return CompareTo(other) == 0; }
            bool operator != (const VARIANT& other) const { HWIN_TRACE();return CompareTo(other) != 0; }
            bool operator <= (const VARIANT& other) const { HWIN_TRACE();return CompareTo(other) <= 0; }
            bool operator <  (const VARIANT& other) const { HWIN_TRACE();return CompareTo(other) <  0; }
            bool operator >  (const VARIANT& other) const { HWIN_TRACE();return CompareTo(other) >  0; }
            bool operator >= (const VARIANT& other) const { HWIN_TRACE();return CompareTo(other) >= 0; }

            bool operator == (const Variant& other) const { HWIN_TRACE();return CompareTo(other) == 0; }
            bool operator != (const Variant& other) const { HWIN_TRACE();return CompareTo(other) != 0; }
            bool operator <= (const Variant& other) const { HWIN_TRACE();return CompareTo(other) <= 0; }
            bool operator <  (const Variant& other) const { HWIN_TRACE();return CompareTo(other) <  0; }
            bool operator >  (const Variant& other) const { HWIN_TRACE();return CompareTo(other) >  0; }
            bool operator >= (const Variant& other) const { HWIN_TRACE();return CompareTo(other) >= 0; }
            

            HRESULT AssignTo( VARIANT& other ) const
            {
                HRESULT result = S_OK;
                if(other.vt != VT_EMPTY)
                {
                    result = VariantClear(&other);
                    if(FAILED(result))
                    {
                        return result;
                    }
                    other.vt = VT_EMPTY;
                }
                if(IsEmpty() == FALSE)
                {
                    result = VariantCopy(&other,this);
                }
                return result;
            }

            Variant& Assign(INT64 theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Int64);
                llVal = theValue;
                return *this;
            }
            Variant& Assign(UINT64 theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::UInt64);
                ullVal = theValue;
                return *this;
            }
            Variant& Assign(LONG theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Long);
                lVal = theValue;
                return *this;
            }

            Variant& Assign(LONG theValue, VariantType variantType )
            {
                HWIN_TRACE();
                Clear();
                if( variantType == VariantType::Long || variantType == VariantType::Error )
                {
                    SetVariantType(variantType);
                    lVal = theValue;
                }
                else
                {
                    SetVariantType(VariantType::Error);
                    scode = E_INVALIDARG;
                    CheckHRESULT(scode);
                }
                return *this;
            }

            Variant& Assign(BYTE theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Byte);
                bVal = theValue;
                return *this;
            }
            Variant& Assign(SHORT theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Short);
                iVal = theValue;
                return *this;
            }
            Variant& Assign(FLOAT theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Float);
                fltVal = theValue;
                return *this;
            }
            Variant& Assign(DOUBLE theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Double);
                dblVal = theValue;
                return *this;
            }

            Variant& Assign(DOUBLE theValue, VariantType variantType )
            {
                HWIN_TRACE();
                Clear();
                if( variantType == VariantType::Double || variantType == VariantType::DateTime )
                {
                    SetVariantType(variantType);
                    dblVal = theValue;
                }
                else
                {
                    SetVariantType(VariantType::Error);
                    scode = E_INVALIDARG;
                    CheckHRESULT(scode);
                }
                return *this;
            }

            Variant& Assign(bool theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Bool);
                boolVal = theValue?VARIANT_TRUE:VARIANT_FALSE;
                return *this;
            }

            Variant& Assign(const CY& theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Currency);
                cyVal = theValue;
                return *this;
            }
            Variant& Assign(const DateTime& theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::DateTime);
                date = theValue.ToOADate();
                return *this;
            }
            Variant& Assign(const TimeSpan& theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Double);
                date = theValue.TotalDays();
                return *this;
            }

            Variant& Assign(const SysString& theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::BStr);
                bstrVal = theValue.Copy();
                return *this;
            }

            Variant& Assign(const String& theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::BStr);
                bstrVal = nullptr;
                if(theValue.length())
                {
                    bstrVal = SysAllocStringLen(theValue.c_str(),UINT(theValue.length()));
                    if(!bstrVal)
                    {
                        CheckHRESULT(E_OUTOFMEMORY);
                    }
                }
                return *this;
            }


            Variant& Assign(IUnknown* pInterface)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Unknown);
                punkVal = pInterface;
                if(punkVal)
                {
                    punkVal->AddRef();
                }
                return *this;
            }

            Variant& Assign(IDispatch* pInterface)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Dispatch);
                pdispVal = pInterface;
                if(pdispVal)
                {
                    pdispVal->AddRef();
                }
                return *this;
            }

            Variant& Assign(SAFEARRAY* safeArray)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Array);
                if(safeArray)
                {
                    auto vType = SafeArray::GetElementVariantType(safeArray);
                    SetVariantType(VariantType::Array | vType);
                    auto hr = SafeArrayCopy(safeArray,&parray);
                    CheckHRESULT(hr);
                }
                else
                {
                    parray = nullptr;
                }
                return *this;
            }

            Variant& Assign(CHAR theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::SByte);
                cVal = theValue;
                return *this;
            }

            Variant& Assign(USHORT theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::UShort);
                uiVal = theValue;
                return *this;
            }

            Variant& Assign(ULONG theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::ULong);
                ulVal = theValue;
                return *this;
            }

            template <typename T>
            Variant& operator = (const T& theValue)
            {
                HWIN_TRACE();
                return Assign(theValue);
            }

            Variant Abs() const
            {
                HWIN_TRACE();
                Variant result;
                auto hr = VarAbs((LPVARIANT)this,&result);
                if(FAILED(hr))
                {
                    CheckHRESULT(hr);
                }
                return result;
            }

            Variant Add(const Variant& other) const
            {
                HWIN_TRACE();
                Variant result;
                auto hr = VarAdd((LPVARIANT)this,(LPVARIANT)&other,&result);
                if(FAILED(hr))
                {
                    CheckHRESULT(hr);
                }
                return result;
            }

            friend Variant operator + (const Variant& left, const Variant& right)
            {
                HWIN_TRACE();
                return left.Add(right);
            }

            Variant And(const Variant& other) const
            {
                HWIN_TRACE();
                Variant result;
                auto hr = VarAnd((LPVARIANT)this,(LPVARIANT)&other,&result);
                if(FAILED(hr))
                {
                    CheckHRESULT(hr);
                }
                return result;
            }

            friend Variant operator & (const Variant& left, const Variant& right)
            {
                HWIN_TRACE();
                return left.And(right);
            }

            Variant Concatenate(const Variant& other) const
            {
                HWIN_TRACE();
                Variant result;
                auto hr = VarCat((LPVARIANT)this,(LPVARIANT)&other,&result);
                if(FAILED(hr))
                {
                    CheckHRESULT(hr);
                }
                return result;
            }


            Variant Div(const Variant& other) const
            {
                HWIN_TRACE();
                Variant result;
                auto hr = VarDiv((LPVARIANT)this,(LPVARIANT)&other,&result);
                if(FAILED(hr))
                {
                    CheckHRESULT(hr);
                }
                return result;
            }

            friend Variant operator / (const Variant& left, const Variant& right)
            {
                HWIN_TRACE();
                return left.Div(right);
            }


            Variant IDiv(const Variant& other) const
            {
                HWIN_TRACE();
                Variant result;
                auto hr = VarIdiv((LPVARIANT)this,(LPVARIANT)&other,&result);
                if(FAILED(hr))
                {
                    CheckHRESULT(hr);
                }
                return result;
            }

            Variant Trunc() const
            {
                HWIN_TRACE();
                Variant result;
                auto hr = VarFix((LPVARIANT)this,&result);
                if(FAILED(hr))
                {
                    CheckHRESULT(hr);
                }
                return result;
            }

            Variant Mod(const Variant& other) const
            {
                HWIN_TRACE();
                Variant result;
                auto hr = VarMod((LPVARIANT)this,(LPVARIANT)&other,&result);
                if(FAILED(hr))
                {
                    CheckHRESULT(hr);
                }
                return result;
            }


            friend Variant operator % (const Variant& left, const Variant& right)
            {
                HWIN_TRACE();
                return left.Mod(right);
            }

            Variant Mul(const Variant& other) const
            {
                HWIN_TRACE();
                Variant result;
                auto hr = VarMul((LPVARIANT)this,(LPVARIANT)&other,&result);
                if(FAILED(hr))
                {
                    CheckHRESULT(hr);
                }
                return result;
            }

            friend Variant operator * (const Variant& left, const Variant& right)
            {
                HWIN_TRACE();
                return left.Mod(right);
            }

            Variant Neg() const
            {
                HWIN_TRACE();
                Variant result;
                auto hr = VarNeg((LPVARIANT)this,&result);
                if(FAILED(hr))
                {
                    CheckHRESULT(hr);
                }
                return result;
            }

            Variant Not() const
            {
                HWIN_TRACE();
                Variant result;
                auto hr = VarNot((LPVARIANT)this,&result);
                if(FAILED(hr))
                {
                    CheckHRESULT(hr);
                }
                return result;
            }

            Variant operator ! () const
            {
                HWIN_TRACE();
                return Not();
            }


            Variant Or(const Variant& other) const
            {
                HWIN_TRACE();
                Variant result;
                auto hr = VarOr((LPVARIANT)this,(LPVARIANT)&other,&result);
                if(FAILED(hr))
                {
                    CheckHRESULT(hr);
                }
                return result;
            }

            friend Variant operator | (const Variant& left, const Variant& right)
            {
                HWIN_TRACE();
                return left.Mod(right);
            }

            Variant Sub(const Variant& other) const
            {
                HWIN_TRACE();
                Variant result;
                auto hr = VarOr((LPVARIANT)this,(LPVARIANT)&other,&result);
                if(FAILED(hr))
                {
                    CheckHRESULT(hr);
                }
                return result;
            }

            friend Variant operator - (const Variant& left, const Variant& right)
            {
                HWIN_TRACE();
                return left.Sub(right);
            }


        };

        // ----------------------------------------------------------------------
        // PropertyVariant
        // ----------------------------------------------------------------------
        class PropertyVariant : public PROPVARIANT
        {
            void SetVariantType(VariantType variantType)
            {
                HWIN_TRACE();
                vt = USHORT(variantType);
            }
        public:
            // Temporary solution, will probably not work well for those types
            // that are specific to PROPVARIANT.
            // Relies on the fact that VARIANT and PROPVARIANT have the same 
            // binary layout.  
            static int Compare(const PROPVARIANT& first,const PROPVARIANT& second)
            {
                HWIN_TRACE();
                if(first.vt == USHORT(VariantType::Empty))
                {
                    if(second.vt == USHORT(VariantType::Empty))
                    {
                        return 0;
                    }
                    return -1;
                }
                else if(second.vt == USHORT(VariantType::Empty))
                {
                    return 1;
                }
                else if(first.vt == USHORT(VariantType::Null))
                {
                    if(second.vt == USHORT(VariantType::Null))
                    {
                        return 0;
                    }
                    return -1;
                }
                else if(second.vt == USHORT(VariantType::Null))
                {
                    return 1;
                }

                auto lcid = GetUserDefaultLCID();
                auto result = VarCmp((VARIANT*)&first,(VARIANT*)&second,lcid);
                if(FAILED(result))
                {
                    CheckHRESULT(result);
                }
                return result - 1;
            }

            VariantType Type() const
            {
                HWIN_TRACE();
                return VariantType(vt);
            }

            VariantType ElementType() const
            {
                HWIN_TRACE();
                return VariantType(vt & VT_TYPEMASK);
            }

            bool IsEmpty() const
            {
                HWIN_TRACE();
                return Type() == VariantType::Empty;
            }

            bool IsNull() const
            {
                HWIN_TRACE();
                return Type() == VariantType::Null;
            }

            bool IsReference() const
            {
                HWIN_TRACE();
                return (Type() & VariantType::Byref) != VariantType::Empty;
            }

            bool IsArray() const
            {
                HWIN_TRACE();
                return (Type() & VariantType::Array) != VariantType::Empty;
            }



            PropertyVariant()
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Empty);
            }
            PropertyVariant(INT64 theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Int64);
                hVal.QuadPart = theValue;
            }
            PropertyVariant(UINT64 theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::UInt64);
                uhVal.QuadPart = theValue;
            }
            PropertyVariant(LONG theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Long);
                lVal = theValue;
            }

            PropertyVariant(LONG theValue, VariantType variantType )
            {
                HWIN_TRACE();
                if( variantType == VariantType::Long || variantType == VariantType::Error )
                {
                    SetVariantType(variantType);
                    lVal = theValue;
                }
                else
                {
                    SetVariantType(VariantType::Error);
                    scode = E_INVALIDARG;
                    CheckHRESULT(scode);
                }
            }

            PropertyVariant(BYTE theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Byte);
                bVal = theValue;
            }
            PropertyVariant(SHORT theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Short);
                iVal = theValue;
            }
            PropertyVariant(FLOAT theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Float);
                fltVal = theValue;
            }
            PropertyVariant(DOUBLE theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Double);
                dblVal = theValue;
            }

            PropertyVariant(DOUBLE theValue, VariantType variantType )
            {
                HWIN_TRACE();
                if( variantType == VariantType::Double || variantType == VariantType::DateTime )
                {
                    SetVariantType(variantType);
                    dblVal = theValue;
                }
                else
                {
                    SetVariantType(VariantType::Error);
                    scode = E_INVALIDARG;
                    CheckHRESULT(scode);
                }
            }

            PropertyVariant(bool theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Bool);
                boolVal = theValue?VARIANT_TRUE:VARIANT_FALSE;
            }

            PropertyVariant(const CY& theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Currency);
                cyVal = theValue;
            }
            PropertyVariant(const DateTime& theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::DateTime);
                date = theValue.ToOADate();
            }
            PropertyVariant(const TimeSpan& theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::DateTime);
                date = theValue.TotalDays();
            }

            PropertyVariant(const FILETIME& theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::FileTime);
                filetime = theValue;
            }

            PropertyVariant(const SysString& theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::BStr);
                bstrVal = theValue.Copy();
            }

            PropertyVariant(IUnknown* pInterface)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Unknown);
                punkVal = pInterface;
                if(punkVal)
                {
                    punkVal->AddRef();
                }
            }

            PropertyVariant(IDispatch* pInterface)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Dispatch);
                pdispVal = pInterface;
                if(pdispVal)
                {
                    pdispVal->AddRef();
                }
            }

            PropertyVariant(SAFEARRAY* safeArray)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::Array);
                if(safeArray)
                {
                    auto vType = SafeArray::GetElementVariantType(safeArray);
                    SetVariantType(VariantType::Array | vType);
                    auto hr = SafeArrayCopy(safeArray,&parray);
                    CheckHRESULT(hr);
                }
                else
                {
                    parray = nullptr;
                }
            }

            PropertyVariant(CHAR theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::SByte);
                cVal = theValue;
            }

            PropertyVariant(USHORT theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::UShort);
                uiVal = theValue;
            }

            PropertyVariant(ULONG theValue)
            {
                HWIN_TRACE();
                SetVariantType(VariantType::ULong);
                ulVal = theValue;
            }

            PropertyVariant(const PropertyVariant& other)
            {
                HWIN_TRACE();
                PropVariantInit(this);
                auto hr = PropVariantCopy(this,&other);
                CheckHRESULT(hr);
            }

            PropertyVariant(PropertyVariant&& other)
            {
                HWIN_TRACE();
                memcpy(&vt,&other.vt,sizeof(VARIANT));
                memset(&other.vt,0,sizeof(VARIANT));
            }

            PropertyVariant& Clear()
            {
                HWIN_TRACE();
                switch(vt)
                {
                case VT_EMPTY:
                case VT_NULL:
                case VT_I1:
                case VT_I2:
                case VT_I4:
                case VT_I8:
                case VT_UI1:
                case VT_UI2:
                case VT_UI4:
                case VT_UI8:
                case VT_R4:
                case VT_R8:
                case VT_CY:
                case VT_DATE:
                    vt = VT_EMPTY;
                    break;
                default:
                    auto hr = PropVariantClear(this);
                    CheckHRESULT(hr);
                    break;

                }
                return *this;
            }

            ~PropertyVariant()
            {
                HWIN_TRACE();
                if(vt != VT_EMPTY)
                {
                    Clear();
                }
            }

            PropertyVariant& operator = (const PropertyVariant& other)
            {
                HWIN_TRACE();
                if(&vt != &other.vt)
                {
                    if(vt != VT_EMPTY)
                    {
                        Clear();
                    }
                    auto hr = PropVariantCopy(this,&other);
                    CheckHRESULT(hr);
                }
                return *this;
            }

            PropertyVariant& operator = (PropertyVariant&& other)
            {
                HWIN_TRACE();
                if(this != &other)
                {
                    if(vt != VT_EMPTY)
                    {
                        Clear();
                    }
                    memcpy(&vt,&other.vt,sizeof(VARIANT));
                    memset(&other.vt,0,sizeof(VARIANT));
                }
            }

            PropertyVariant& Assign(INT64 theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Int64);
                hVal.QuadPart = theValue;
                return *this;
            }
            PropertyVariant& Assign(UINT64 theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::UInt64);
                uhVal.QuadPart = theValue;
                return *this;
            }
            PropertyVariant& Assign(LONG theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Long);
                lVal = theValue;
                return *this;
            }

            PropertyVariant& Assign(LONG theValue, VariantType variantType )
            {
                HWIN_TRACE();
                Clear();
                if( variantType == VariantType::Long || variantType == VariantType::Error )
                {
                    SetVariantType(variantType);
                    lVal = theValue;
                }
                else
                {
                    SetVariantType(VariantType::Error);
                    scode = E_INVALIDARG;
                    CheckHRESULT(scode);
                }
                return *this;
            }

            PropertyVariant& Assign(BYTE theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Byte);
                bVal = theValue;
                return *this;
            }
            PropertyVariant& Assign(SHORT theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Short);
                iVal = theValue;
                return *this;
            }
            PropertyVariant& Assign(FLOAT theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Float);
                fltVal = theValue;
                return *this;
            }
            PropertyVariant& Assign(DOUBLE theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Double);
                dblVal = theValue;
                return *this;
            }

            PropertyVariant& Assign(DOUBLE theValue, VariantType variantType )
            {
                HWIN_TRACE();
                Clear();
                if( variantType == VariantType::Double || variantType == VariantType::DateTime )
                {
                    SetVariantType(variantType);
                    dblVal = theValue;
                }
                else
                {
                    SetVariantType(VariantType::Error);
                    scode = E_INVALIDARG;
                    CheckHRESULT(scode);
                }
                return *this;
            }

            PropertyVariant& Assign(bool theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Bool);
                boolVal = theValue?VARIANT_TRUE:VARIANT_FALSE;
                return *this;
            }

            PropertyVariant& Assign(const CY& theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Currency);
                cyVal = theValue;
                return *this;
            }
            PropertyVariant& Assign(const DateTime& theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::DateTime);
                date = theValue.ToOADate();
                return *this;
            }
            PropertyVariant& Assign(const TimeSpan& theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Double);
                date = theValue.TotalDays();
                return *this;
            }

            PropertyVariant& Assign(const SysString& theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::BStr);
                bstrVal = theValue.Copy();
                return *this;
            }

            PropertyVariant& Assign(IUnknown* pInterface)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Unknown);
                punkVal = pInterface;
                if(punkVal)
                {
                    punkVal->AddRef();
                }
                return *this;
            }

            PropertyVariant& Assign(IDispatch* pInterface)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Dispatch);
                pdispVal = pInterface;
                if(pdispVal)
                {
                    pdispVal->AddRef();
                }
                return *this;
            }

            PropertyVariant& Assign(SAFEARRAY* safeArray)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::Array);
                if(safeArray)
                {
                    auto vType = SafeArray::GetElementVariantType(safeArray);
                    SetVariantType(VariantType::Array | vType);
                    auto hr = SafeArrayCopy(safeArray,&parray);
                    CheckHRESULT(hr);
                }
                else
                {
                    parray = nullptr;
                }
                return *this;
            }

            PropertyVariant& Assign(CHAR theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::SByte);
                cVal = theValue;
                return *this;
            }

            PropertyVariant& Assign(USHORT theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::UShort);
                uiVal = theValue;
                return *this;
            }

            PropertyVariant& Assign(ULONG theValue)
            {
                HWIN_TRACE();
                Clear();
                SetVariantType(VariantType::ULong);
                ulVal = theValue;
                return *this;
            }

        };
    }
}

#endif //__HWINVARIANT_H__